/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
	status = PENDING
	value = undefined
	reason = undefined
	successCallbacks = []
	rejectCallbacks = []

	constructor(executor) {
		try {
			executor(this.resolve, this.reject)
		} catch (e) {
			this.reject(e)
		}
	}

	resolve = value => {
		if (this.status !== PENDING) return
		this.status = FULFILLED
		this.value = value

		while (this.successCallbacks.length) {
			this.successCallbacks.shift()(this.value)
		}
	}

	reject = reason => {
		if (this.status !== PENDING) return
		this.status = REJECTED
		this.reason = reason

		while (this.rejectCallbacks.length) {
			this.rejectCallbacks.shift()(this.reason)
		}
	}

	then(successCallback, rejectCallback) {
		const promise2 = new MyPromise((resolve, reject) => {
			if (this.status === FULFILLED) {
				setTimeout(() => {
					try {
						const result = successCallback(this.value)
						this._resolvePromise(promise2, result, resolve, reject)
					} catch (e) {
						reject(e)
					}
				}, 0)
			} else if (this.status === REJECTED) {
				setTimeout(() => {
					try {
						const reason = rejectCallback(this.reason)
						this._resolvePromise(promise2, reason, resolve, reject)
					} catch (e) {
						reject(e)
					}
				}, 0)
			} else {
				this.successCallbacks.push(() => {
					setTimeout(() => {
						try {
							const result = successCallbacks(this.value)
							this._resolvePromise(promise2, result, resolve, reject)
						} catch (e) {
							reject(e)
						}
					}, 0)
				})
				this.rejectCallbacks.push(() => {
					setTimeout(() => {
						try {
							const reason = rejectCallback(this.reason)
							this._resolvePromise(promise2, reason, resolve, reject)
						} catch (e) {
							reject(e)
						}
					}, 0)
				})
			}
		})
		return promise2
	}

	_resolvePromise(self, value, resolve, reject) {
		if (self === value) {
			reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
		}
		if (value instanceof MyPromise) {
			value.then(resolve, reject)
		} else {
			resolve(value)
		}
	}

	catch(rejectCallback) {
		this.then(undefined, rejectCallback)
	}

	finally(callback) {
		return this.then(
			value => {
				return MyPromise.resolve(callback()).then(() => value)
			},
			reason => {
				return MyPromise.resolve(callback()).then(() => {
					throw reason
				})
			}
		)
	}

	static all(array) {
		const result = []
		const index = 0
		return new MyPromise((resolve, reject) => {
			function addData(key, value) {
				result[key] = value
				index++
				if (index === array.length) {
					resolve(result)
				}
			}
			for (let i = 0; i < array.length; i++) {
				const current = array[i]
				if (current instanceof MyPromise) {
					current.then(
						value => addData(i, value),
						reason => reject(reason)
					)
				} else {
					addData(i, array[i])
				}
			}
		})
	}

	static resolve(value) {
		if (value instanceof MyPromise) {
			return value
		}
		return new MyPromise(resolve => resolve(value))
	}
}

module.exports = MyPromise
